module digital_recognition #(
    parameter NUM_ROW =  1 ,
    parameter NUM_COL =  4 ,
    parameter H_PIXEL = 480,
    parameter V_PIXEL = 272,
    parameter NUM_WIDTH = (NUM_ROW*NUM_COL<<2)-1
)(
    //module clock
    input                    clk              ,  // 时钟信号
    input                    rst_n            ,  // 复位信号（低有效）

    //image data interface
    input                    monoc            ,  // 单色图像像素数据
    input                    monoc_fall       ,  // 图像数据变化
    input      [10:0]        xpos             ,
    input      [10:0]        ypos             ,
    output reg [15:0]        color_rgb        ,

    //project border ram interface
    input      [10:0]        row_border_data  ,
    output reg [10:0]        row_border_addr  ,
    input      [10:0]        col_border_data  ,
    output reg [10:0]        col_border_addr  ,

    //user interface
    input      [ 1:0]        frame_cnt        ,  // 当前帧
    input                    project_done_flag,  // 投影完成标志
    input      [ 3:0]        num_col          ,  // 采集到的数字列数
    input      [ 3:0]        num_row          ,  // 采集到的数字行数
    output reg [NUM_WIDTH:0] digit               // 识别到的数字
);

//localparam define
localparam FP_1_3 = 6'b010101;                   // 1/3 小数的定点化   纯小数的定点化  没有整数位
localparam FP_2_3 = 6'b101011;                   // 2/3 
localparam FP_2_5 = 6'b011010;                   // 2/5 
localparam FP_3_5 = 6'b100110;                   // 3/5 
localparam NUM_TOTAL = NUM_ROW * NUM_COL - 1'b1; // 需识别的数字共个数，始于0  总共四个

//reg define
reg  [10:0]        col_border_l                    ;
reg  [10:0]        col_border_r                    ;
reg  [10:0]        row_border_hgh                  ;
reg  [10:0]        row_border_low                  ;
reg  [16:0]        row_border_hgh_t                ;
reg  [16:0]        row_border_low_t                ;
reg                x1_l     [NUM_TOTAL:0]          ;
reg                x1_r     [NUM_TOTAL:0]          ;
reg                x2_l     [NUM_TOTAL:0]          ;
reg                x2_r     [NUM_TOTAL:0]          ;
reg  [ 1:0]        y        [NUM_TOTAL:0]          ;
reg  [ 1:0]        y_flag   [NUM_TOTAL:0]          ;
reg                row_area [NUM_ROW - 1'b1:0]     ;  // 行区域
reg                col_area [NUM_TOTAL     :0]     ;  // 列区域
reg  [ 3:0]        row_cnt,row_cnt_t               ;
reg  [ 3:0]        col_cnt,col_cnt_t               ;
reg  [11:0]        cent_y_t                        ;
reg  [10:0]        v25                             ;  // 行边界的2/5
reg  [10:0]        v23                             ;  // 行边界的2/3
reg  [22:0]        v25_t                           ;
reg  [22:0]        v23_t                           ;
reg  [ 5:0]        num_cnt/*synthesis keep*/       ;
reg                row_d0,row_d1                   ;
reg                col_d0,col_d1                   ;
reg                row_chg_d0,row_chg_d1,row_chg_d2;
reg                row_chg_d3                      ;
reg                col_chg_d0,col_chg_d1,col_chg_d2;
reg  [ 7:0]        real_num_total                  ;
reg  [ 3:0]        digit_id/*synthesis keep*/      ;
reg  [ 3:0]        digit_cnt                       ;
reg  [NUM_WIDTH:0] digit_t                         ;

//wire define
reg  [10:0] cent_y/*synthesis keep*/;
wire        y_flag_fall ;
wire        col_chg     ;
wire        row_chg     ;
wire        feature_deal;

//*****************************************************
//**                    main code
//*****************************************************
assign row_chg = row_d0 ^ row_d1;
assign col_chg = col_d0 ^ col_d1;
assign y_flag_fall  = ~y_flag[num_cnt][0] & y_flag[num_cnt][1];
assign feature_deal = project_done_flag && frame_cnt == 2'd2; // 处理特征   投影结束并且处在第三帧图片的同步 

//实际采集到的数字总数
always @(*) begin
    if(project_done_flag)
        real_num_total = num_col * num_row;
end
//  assign  real_num_total= project_done_flag ? num_col*num_row : 0;  这两段代码一样  但是real_num_total 是 reg 型变量只能用alway语句进行赋值

//检测行变化
always @(posedge clk) begin
    if(project_done_flag) begin
        row_cnt_t <= row_cnt;
        row_d1    <= row_d0 ;
        if(row_cnt_t != row_cnt)
            row_d0 <= ~row_d0;
    end
    else begin
        row_d0 		<= 1'b1;
        row_d1 		<= 1'b1;
        row_cnt_t 	<= 4'hf;
    end
end

//获取数字的行边界   一行数字对应两个边界  如果只有一行的话 只对应两个地址 上边界地址和下边界地址
always @(posedge clk) begin
    if(row_chg)//row_chg高电平有效，表示已经完成一个上边界的读取，地址更新，读取下一个边界坐标信息  ，行边界读地址要回传给projection模块作为边界ram的读地址
        row_border_addr <= (row_cnt << 1'b1) + 1'b1;  //这里是为了获得奇数  保证最后一位1 （奇数上边界）
    else
        row_border_addr <= row_cnt << 1'b1+2'd2;    //下边界读地址  ；  上边界和下边界是都被读出的（偶数下边界）
end

always @(posedge clk) begin
    if(row_border_addr[0])  //在边界RAM中  奇数地址存上边界，偶数地址存下边界
        row_border_hgh <= row_border_data;  //奇数地址接收上边界的坐标信息
    else
        row_border_low <= row_border_data;  //偶数地址接收下边界的坐标信息（在图像中的坐标信息）
end

always @(posedge clk) begin
    row_chg_d0 <= row_chg;
    row_chg_d1 <= row_chg_d0;
    row_chg_d2 <= row_chg_d1;
    row_chg_d3 <= row_chg_d2;
end

//检测列变化   与行检测边界一样的分析思路  要确保边界信息已经被存储到了边界RAM中  这里只需要生成读地址信号将边界信息读取出来即可
always @(posedge clk) begin
    if(project_done_flag) begin        //投影结束只需要一行像素同步时间  剩下的就是帧时间就是做数字处理与识别的时间
        col_cnt_t <= col_cnt;
        col_d1    <= col_d0;
        if(col_cnt_t != col_cnt)
            col_d0 <= ~col_d0;
    end
    else begin
        col_d0 <= 1'b1;
        col_d1 <= 1'b1;
        col_cnt_t <= 4'hf;
    end
end

//获取单个数字的列边界
always @(posedge clk) begin
    if(col_chg)   //col_chg高有效  列计数器变化时拉高  并更新地址  读取下一个边界坐标信息
        col_border_addr <= (col_cnt << 1'b1) + 1'b1;
    else
        col_border_addr <= col_cnt << 1'b1+2'd2;
end

always @(posedge clk) begin
    if(col_border_addr[0])
        col_border_l <= col_border_data;  //（奇数地址存左边界）
    else
        col_border_r <= col_border_data; //偶数地址存右边界
end

always @(posedge clk) begin  //将col_chg（更新边界信息的标志信号）打三拍  
    col_chg_d0 <= col_chg;
    col_chg_d1 <= col_chg_d0;
    col_chg_d2 <= col_chg_d1;
end


//数字中心y
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cent_y_t <= 12'd0;
    else if(project_done_flag) begin
        if(col_chg_d1)
            cent_y_t <= col_border_l + col_border_r;
        if(col_chg_d2)
            cent_y <= cent_y_t[11:1];  //相当于右移1位 （col_border_l + col_border_r）/2 找出每一个边界的中心
    end
end

//x1、x2
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        v25 <= 11'd0;
        v23 <= 11'd0;
        v25_t <= 23'd0;
        v23_t <= 23'd0;
        row_border_hgh_t <= 17'b0;
        row_border_low_t <= 17'b0;
    end
    else if(project_done_flag) begin
        if(row_chg_d1) begin
            row_border_hgh_t <= { row_border_hgh,6'b0};  //第二拍两个数据均已被同步  所以要在第二拍进行操作  增添6位小数位 做定点运算 （这里不加其实也行）
            row_border_low_t <= { row_border_low,6'b0};
        end
        if(row_chg_d2) begin  //第三拍做计算
            v25_t <= row_border_low_t * FP_2_5 + row_border_hgh_t * FP_3_5;// x1 
            v23_t <= row_border_low_t * FP_2_3 + row_border_hgh_t * FP_1_3;// x2
        end
        if(row_chg_d3) begin  //第四拍提取x1、x2的坐标信息
            v25 <= v25_t[22:12];
            v23 <= v23_t[22:12];
        end
    end
end

//行区域
always @(*) begin
    row_area[row_cnt] = xpos >= row_border_hgh && xpos <= row_border_low;  // 找出行区域  row_area[row_cnt] 为高表示数字的第row_cnt+1行区域（x的像素区域）
end

//列区域
always @(*) begin
    col_area[col_cnt] = ypos >= col_border_l   && ypos <= col_border_r;  //找出列区域  col_area[0] 表示第一个数字的列像素区域 
end

//确定col_cnt
always @(posedge clk) begin
    if(project_done_flag) begin
        if(row_area[row_cnt] && ypos == col_border_r)
            col_cnt <= col_cnt == num_col - 1'b1 ? 'd0 : col_cnt + 1'b1;  //达到右边界更新计数值  此时将下一个边界信息读取出来
    end
    else
        col_cnt <= 4'd0;
end

//确定row_cnt
always @(posedge clk) begin
    if(project_done_flag) begin  //必须等投影完成是因为要确保已经将边界信息存放在边界RAM中
        if(xpos == row_border_hgh + 1'b1)//坐标达到上边界表示已经扫描到了边界 这时可更新下一个边界
            row_cnt <= row_cnt == num_row - 1'b1 ? 'd0 : row_cnt + 1'b1;
    end
    else
        row_cnt <= 12'd0;
end

//num_cnt用于清零特征点和计数特征点
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        num_cnt <= 'd0;
    else if(feature_deal)
        num_cnt <= row_cnt * num_col + col_cnt;   //对特征点进行计数（做偏移） 
    else if(feature_deal=='b0 && num_cnt <= NUM_TOTAL)
        num_cnt <= num_cnt + 1'b1;
    else
        num_cnt <= 'd0;
end

//x1与x2的特征数
always @(posedge clk) begin
    if(feature_deal) begin
        if(xpos == v25) begin
            if(ypos >= col_border_l && ypos <= cent_y && monoc_fall)  //monoc_fall:表示检测到了下降沿 像素从1->0（在ypos >= col_border_l && ypos <= cent_y像素区域内有黑像素点）  X1_l[i]拉高表示第i个数字与x1特征线有一个左交点
                x1_l[num_cnt] <= 1'b1;
            else if(ypos > cent_y && ypos < col_border_r && monoc_fall)//monoc_fall:表示检测到了下降沿 像素从1->0  X1_r[i]拉高表示第i个数字与x1特征线有一个右交点
                x1_r[num_cnt] <= 1'b1;
        end
        else if(xpos == v23) begin
            if(ypos >= col_border_l && ypos <= cent_y && monoc_fall) //monoc_fall:表示检测到了下降沿 像素从1->0  X2_l[i]拉高表示第i个数字与x1特征线有一个左交点
                x2_l[num_cnt] <= 1'b1;
            else if(ypos > cent_y && ypos < col_border_r && monoc_fall)//monoc_fall:表示检测到了下降沿 像素从1->0  X2_r[i]拉高表示第i个数字与x1特征线有一个右交点
                x2_r[num_cnt] <= 1'b1;
        end
    end
    else begin
        x1_l[num_cnt] <= 1'b0;
        x1_r[num_cnt] <= 1'b0;
        x2_l[num_cnt] <= 1'b0;
        x2_r[num_cnt] <= 1'b0;
    end
end

//寄存y_flag，找下降沿
always @(posedge clk) begin
    if(feature_deal) begin
        if(row_area[row_cnt] && ypos == cent_y)
            y_flag[num_cnt] <= {y_flag[num_cnt][0],monoc};  //如果中心点是像素是0表示有一个交点（与y特征线）有下降沿表示中心像素为黑
    end
    else
        y_flag[num_cnt] <= 2'd3;
end

//Y方向的特征数
always @(posedge clk) begin
    if(feature_deal) begin
        if(ypos == cent_y + 1'b1 && y_flag_fall)  //中心点像素为黑表示有一个交点  对应数字的Y特征数+1
            y[num_cnt] <= y[num_cnt] + 1'd1;  //记录每个特征数Y方向上的特征点
    end
    else
        y[num_cnt] <= 2'd0;
end

//特征匹配    对缓冲块中寄存特征信息做匹配  从而确定是什么数字
always @(*) begin
    case({y[digit_cnt],x1_l[digit_cnt],x1_r[digit_cnt],x2_l[digit_cnt],x2_r[digit_cnt]}) //y[i]、x1_l[i]、x1_r[i]、x2_l[i]、x2_r[i]分别表示第i个数字y、x1、x2的三个特征信息
        6'b10_1_1_1_1: digit_id = 4'h0; //0
        6'b01_1_0_1_0: digit_id = 4'h1; //1
        6'b11_0_1_1_0: digit_id = 4'h2; //2
        6'b11_0_1_0_1: digit_id = 4'h3; //3
        6'b10_1_1_1_0: digit_id = 4'h4; //4
        6'b11_1_0_0_1: digit_id = 4'h5; //5
        6'b11_1_0_1_1: digit_id = 4'h6; //6
        6'b10_0_1_1_0: digit_id = 4'h7; //7
        6'b11_1_1_1_1: digit_id = 4'h8; //8
        6'b11_1_1_0_1: digit_id = 4'h9; //9
        default: digit_id <= 4'h0;
    endcase
end

//识别数字
always @(posedge clk) begin
    if(feature_deal && xpos == row_border_low + 1'b1) begin  //要确保在行的下边界读取x1,x2,y三个缓冲块的相应位置的值，因为这些缓冲块是在数字行区域内赋值的，要在边界外读取相应值
        if(real_num_total == 1'b1)
            digit_t <= digit_id;
        else if(digit_cnt < real_num_total) begin
            digit_cnt <= digit_cnt + 1'b1; //为了读取缓冲块相应位置的数据
            digit_t   <= {digit_t[NUM_WIDTH-4:0],digit_id};
        end
    end
    else begin
        digit_cnt <= 'd0;
        digit_t   <= 'd0;
    end
end

//输出识别到的数字
always @(posedge clk) begin
    if(feature_deal && digit_cnt == real_num_total)
        digit <= digit_t;  //这是相邻数字的拼接，高4位是前面一个数字、低四位是后面一个数字
end

//输出边界和图像
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        color_rgb <= 16'h0000;
    else if(row_area[row_cnt] && ( ypos == col_border_l || ypos == col_border_r ||
            ypos == (col_border_l -1) || ypos == (col_border_r+1)))
        color_rgb <= 16'hf800; //左右竖直边界线   
    else if(col_area[col_cnt] && (xpos == row_border_low || xpos== row_border_hgh ||
            xpos==( row_border_low + 1) || xpos== (row_border_hgh - 1)))
        color_rgb <= 16'hf800; //上下水平边界线     通过上下左右的边界 可以把每个数字用框框起来  
    else if(monoc)  //对二值图像进行还原  1：-> 白  0：-> 黑
        color_rgb <= 16'hffff; //white
    else
        color_rgb <= 16'h0000; //dark
end

endmodule